package jssia.database.verification;

import static com.smoed.common.utilities.StringUtils.NEWLINE;
import static com.smoed.common.utilities.StringUtils.TAB;

import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import jssia.config.yaml.ConfigurationFactory;
import jssia.database.ColumnInfo;
import jssia.database.IndexInfo;
import jssia.database.TableInfo;
import jssia.database.mysql.columns.CharColumn;
import jssia.database.mysql.columns.DecimalColumn;
import jssia.database.mysql.columns.EnumBackedColumn;
import jssia.database.mysql.columns.VarCharColumn;
import jssia.database.mysql.constants.Collation;
import jssia.database.mysql.constants.IndexStructure;
import jssia.database.mysql.constants.IndexType;
import jssia.database.mysql.constants.TableEngine;

import com.smoed.common.utilities.FileUtils;
import com.smoed.common.utilities.FrameworkUtils;
import com.smoed.common.utilities.StringUtils;

final class TableSourceGenerator {
    private TableSourceGenerator() {
    }

    private final static Map<Class<?>, String> classToImportStringCache = new HashMap<Class<?>, String>();

    static void generateTableSource(Package basePkg, String dbName, Collection<TableSchema> tableSchemas) {
        final File srcLocation = new File(ConfigurationFactory.getJssiaConfiguration().pathToSource);

        final String usersDatabaseSrcLocation = new StringBuilder(srcLocation.getPath())
            .append(File.separatorChar).append(basePkg.getName().replace('.', File.separatorChar)).append(File.separatorChar).append(dbName).append(File.separatorChar).toString();

        File usersDatabaseSrcPath = new File(usersDatabaseSrcLocation);
        if (!usersDatabaseSrcPath.exists()) {
            usersDatabaseSrcPath.mkdirs();
            if (!usersDatabaseSrcPath.exists())
                throw new IllegalStateException("Can not create directory path " + usersDatabaseSrcPath
                        + ".  Please create directory so that I have a place to put generated table source files.");
        }

        final TableInfo defautlTableInfo = FrameworkUtils.getAnnotationDefault(TableInfo.class);
        final ColumnInfo defaultColumnInfo = FrameworkUtils.getAnnotationDefault(ColumnInfo.class);
        final IndexInfo defaultIndexInfo = FrameworkUtils.getAnnotationDefault(IndexInfo.class);

        for (TableSchema tableSchema : tableSchemas) {
            final List<String> tableInfoParams = new ArrayList<String>();

            Set<String> packages = new HashSet<String>();
            packages.add(getPackageImportString(TableInfo.class));

            if (!defautlTableInfo.engine().equals(tableSchema.getEngine())) {
                tableInfoParams.add("engine = TableEngine." + tableSchema.getEngine());
                packages.add(getPackageImportString(TableEngine.class));
            }
            if (tableSchema.getCollation() != null && !defautlTableInfo.collation().equals(tableSchema.getCollation())) {
                tableInfoParams.add("collation = " + tableSchema.getCollation());
                packages.add(getPackageImportString(Collation.class));
            }

            final String tableInfoParamsString = StringUtils.delimitString(tableInfoParams, ", ");

            String tableName = StringUtils.toCamelCase(tableSchema.getName(), "_");
            StringBuilder table = new StringBuilder("@TableInfo (").append(tableInfoParamsString);
            table.append(")").append(NEWLINE).append("public final class ").append(tableName).append(" {").append(NEWLINE).append(NEWLINE)
            .append(TAB).append("private ").append(tableName).append("() {").append(NEWLINE).append(TAB).append("}").append(NEWLINE).append(NEWLINE)
            .append(TAB).append("public final static String TO_STRING = \"").append(tableSchema.getName()).append("\";").append(NEWLINE).append(NEWLINE);



            for (ColumnSchema col : tableSchema.getAllColumns()) {
                packages.add(getPackageImportString(col.getColumnType()));
                table.append(generateColumnSource(col, packages, defaultColumnInfo, defaultIndexInfo));
            }

            List<String> packageImportList = new ArrayList<String>(packages);
            Collections.sort(packageImportList);
            StringBuilder header = new StringBuilder(basePkg.toString())
                .append(".").append(dbName).append(";").append(NEWLINE).append(NEWLINE);
            for (String importStr : packageImportList) {
                header.append(importStr).append(NEWLINE);
            }

            table.append("}").append(NEWLINE);
            header.append(NEWLINE).append(table);

            File tableFile = new File(usersDatabaseSrcLocation + tableName + ".java");
            FileUtils.writeToFile(tableFile, header.toString());
        }

        classToImportStringCache.clear();
    }

    private static String getPackageImportString(Class<?> clas) {
        String importString = classToImportStringCache.get(clas);
        if (importString == null) {
            Package pkg = clas.getPackage();
            if (pkg != null) {
                importString = new StringBuilder("import ")
                    .append(pkg.getName()).append(".").append(clas.getSimpleName()).append(";").toString();
                classToImportStringCache.put(clas, importString);
            }
        }
        return importString;
    }

    private static String generateColumnSource(ColumnSchema colSchema, Set<String> packages, ColumnInfo defaultColumnInfo, IndexInfo defaultIndexInfo) {
        List<String> columnInfoParams = new ArrayList<String>();

        if (colSchema.getCollation() != null && !defaultColumnInfo.collation().equals(colSchema.getCollation()))
            columnInfoParams.add("collation = " + colSchema.getCollation());
        if (!colSchema.isNullable())
            columnInfoParams.add("nullable = false");
        if (colSchema.isAutoIncrement())
            columnInfoParams.add("autoIncrement = true");
        if (colSchema.getDefaultValue().length() > 0)
            columnInfoParams.add("defaultValue = \"" + colSchema.getDefaultValue() + "\"");
        if (colSchema.getIndex() != null)
            columnInfoParams.add(generateIndexSource(colSchema.getIndex(), packages, defaultIndexInfo));

        StringBuilder col = new StringBuilder(TAB);

        if (columnInfoParams.size() > 0) {
            packages.add(getPackageImportString(ColumnInfo.class));
            col.append("@ColumnInfo(").append(StringUtils.delimitString(columnInfoParams, ", ")).append(")").append(NEWLINE).append(TAB);
        }

        col.append("public final static ").append(colSchema.getColumnType().getSimpleName()).append(" ")
            .append(colSchema.getName().toUpperCase()).append(" = new ").append(colSchema.getColumnType().getSimpleName()).append("(");
        if (colSchema.getColumnType().equals(CharColumn.class) || colSchema.getColumnType().equals(VarCharColumn.class))
            col.append(colSchema.getLength().toString()).append(");").append(NEWLINE).append(NEWLINE);
        else if (EnumBackedColumn.class.isAssignableFrom(colSchema.getColumnType())) {
            String backedEnumName = StringUtils.toCamelCase(colSchema.getName(), "_");
            col.append(backedEnumName).append(".class);").append(NEWLINE);
            col.append(TAB).append("// TODO replace auto-generated enum").append(NEWLINE)
                .append(TAB).append("public static enum ").append(backedEnumName).append(" { ").append(NEWLINE).append(TAB).append(TAB)
                .append(StringUtils.delimitString(colSchema.getValueSet(), "," + NEWLINE + TAB + TAB))
                .append(NEWLINE).append(TAB).append("}").append(NEWLINE).append(NEWLINE);
        } else if (colSchema.getColumnType().equals(DecimalColumn.class)) {
            col.append(colSchema.getPrecision()).append(",").append(colSchema.getScale()).append(");").append(NEWLINE).append(NEWLINE);
        } else
            col.append(");").append(NEWLINE).append(NEWLINE);

        return col.toString();
    }

    private static String generateIndexSource(IndexSchema indexSchema, Set<String> packages, IndexInfo defaultIndexInfo) {
        packages.add(getPackageImportString(IndexInfo.class));
        packages.add(getPackageImportString(IndexType.class));

        StringBuilder index = new StringBuilder("index = @IndexInfo(");

        index.append("name = \"").append(indexSchema.getName())
            .append("\", type = IndexType.").append(indexSchema.getType()).append(", ");

        if (!indexSchema.getStructure().equals(defaultIndexInfo.structure())) {
            packages.add(getPackageImportString(IndexStructure.class));
            index.append("structure = IndexStructure.").append(indexSchema.getStructure()).append(", ");
        }

        index.append("columns = {");
        String[] columns = indexSchema.getColumns();
        for (int i = 0; i < columns.length-1; i++) {
            index.append("\"").append(columns[i]).append("\", ");
        }
        index.append("\"").append(columns[columns.length-1]).append("\"})");

        return index.toString();
    }

}
